﻿using System;
using System.Collections.Generic;
using System.Data;

namespace Flagwind.Data
{
	/// <summary>
	/// 表示一个数据命令。
	/// </summary>
	public class DataCommand
	{
		#region 私有字段

		private string _commandText;
		private string _parameterChar;
		private int _parameterLength;
		private IDataParameter[] _parameters;
		private readonly List<string> _originalParams = new List<string>();
		private readonly List<string> _replacedParams = new List<string>();

		#endregion

		#region 保护属性

		protected string ParameterChar
		{
			get
			{
				if(_parameterChar == null)
				{
					return this.DataProvider.ParameterChar;
				}
				return _parameterChar;
			}
			set
			{
				_parameterChar = value;
			}
		}

		protected int ParameterLength
		{
			get
			{
				return _parameterLength;
			}
			set
			{
				_parameterLength = value;
			}
		}

		protected List<string> OriginalParams
		{
			get
			{
				return _originalParams;
			}
		}

		protected List<string> ReplacedParams
		{
			get
			{
				return _replacedParams;
			}
		}

		#endregion

		#region 公共属性

		/// <summary>
		/// 获取或设置命令的名称。
		/// </summary>
		public string CommandName
		{
			get;
			set;
		}

		/// <summary>
		/// 获取或设置命令的类型。
		/// </summary>
		public CommandType CommandType
		{
			get;
			set;
		}

		/// <summary>
		/// 获取或设置命令的内容。
		/// </summary>
		public string CommandText
		{
			get
			{
				return _commandText;
			}
			set
			{
				if(string.IsNullOrWhiteSpace(value))
				{
					throw new ArgumentNullException("value");
				}
				_commandText = this.ResolveCommandText(value);
				_parameters = this.ResolveParameters(value);
				if(this.CommandType == CommandType.StoredProcedure)
				{
					_commandText = Utility.FindWords(_commandText, 0, 255);
				}
			}
		}

		/// <summary>
		/// 获取或设置数据访问提供程序。
		/// </summary>
		public IDataProvider DataProvider
		{
			get;
			set;
		}

		public IDataParameter[] Parameters
		{
			get
			{
				return _parameters;
			}
		}

		#endregion

		#region 构造方法

		public DataCommand(string commandText, CommandType commandType, IDataProvider dataProvider) : this(string.Empty, commandText, commandType, dataProvider)
		{
		}

		public DataCommand(string commandName, string commandText, CommandType commandType, IDataProvider dataProvider)
		{
			this.CommandName = commandName;
			this.DataProvider = dataProvider;
			this.CommandType = commandType;
			this.CommandText = commandText;
		}

		#endregion

		#region 公共方法

		public void SetParameter(string name, object value)
		{
			if(string.IsNullOrWhiteSpace(name))
			{
				throw new ArgumentNullException("name");
			}
			name = this.ParameterChar + name;
			for(int i = 0; i <= _parameters.Length - 1; i++)
			{
				if(_parameters[i].ParameterName == name)
				{
					_parameters[i].Value = value;
					return;
				}
			}
			throw new Exception("未找到指定的参数名：" + name);
		}

		public void SetParameter(string name, string value, ParameterType paramType)
		{
			if(string.IsNullOrWhiteSpace(name))
			{
				throw new ArgumentNullException("name");
			}
			name = this.ParameterChar + name;
			if(paramType == ParameterType.Parameter)
			{
				this.SetParameter(name, value);
			}
			else
			{
				if(string.IsNullOrWhiteSpace(_commandText))
				{
					throw new Exception("无法设置替换参数，可能命令没有初始化．");
				}
				_commandText = _commandText.Replace(this._parameterChar + "%" + name + "%", value);
			}
		}

		public DataSet ExecuteDataSet()
		{
			return this.DataProvider.ExecuteDataSet(this.CommandText, this.CommandType, this.Parameters);
		}

		public T ExecuteEntity<T>() where T : class, IEntity, new()
		{
			var dataReader = this.DataProvider.ExecuteDataReader(this.CommandText, this.CommandType, this.Parameters);
			return DataUtility.ResolveEntity<T>(dataReader);
		}

		public IList<T> ExecuteEntityList<T>() where T : class, IEntity, new()
		{
			var dataReader = this.DataProvider.ExecuteDataReader(this.CommandText, this.CommandType, this.Parameters);
			return DataUtility.ResolveEntityList<T>(dataReader);
		}

		#endregion

		#region 私有方法

		private string ResolveCommandText(string commandText)
		{
			var result = string.Empty;
			var isParameter = true;
			var parameterArray = commandText.Split('#');
			var bufferParameters = new List<string>();
			_parameterLength = 0;
			_originalParams.Clear();
			_replacedParams.Clear();
			for(int i = 0; i <= parameterArray.Length - 1; i++)
			{
				isParameter = !isParameter;
				if(isParameter)
				{
					var bufferArray = parameterArray[i].Split(':');
					var bufferString = this.ParameterChar + bufferArray[0].Trim();
					result += bufferString;
					//处理替换参数
					if(bufferString.Substring(0, 2) == this.ParameterChar + "%" && bufferString.EndsWith("%"))
					{
						_replacedParams.Add(bufferString.Substring(2).Replace("%", ""));
					}
					else
					{
						//处理同名参数
						var bufferParameter = bufferString.ToLower();
						if(!bufferParameters.Contains(bufferParameter))
						{
							bufferParameters.Add(bufferParameter);
							_originalParams.Add(parameterArray[i]);
							_parameterLength += 1;
						}
					}
				}
				else
				{
					result += parameterArray[i];
				}
			}
			return result;
		}

		private IDataParameter[] ResolveParameters(string commandText)
		{
			var parameters = new IDataParameter[this.ParameterLength];
			for(int i = 0; i <= _originalParams.Count - 1; i++)
			{
				var bufferArray = _originalParams[i].Split(':');
				var parameterName = bufferArray[0].Trim();
				var parameterType = "Object";
				if(bufferArray.Length > 1)
				{
					//例如：[System.Type[,System.DbType[,Size[,ParameterDirection]]]]
					var strArrParaTemp = bufferArray[1].Split(',');
					var strSystemDbType = string.Empty;
					var strParameterDirection = string.Empty;
					DbType dbType;
					var size = 0;
					//根据不同的参数值获取参数形式
					switch(strArrParaTemp.Length)
					{
						case 1:
							//声明了属性类型
							parameterType = strArrParaTemp[0];
							parameters[i] = this.DataProvider.GetParameter();
							parameters[i].ParameterName = this.ParameterChar + parameterName;
							parameters[i].DbType = ConvertType(strArrParaTemp[0].Trim());
							break;
						case 2:
							//并且声明了数据字段类型
							parameterType = strArrParaTemp[0];
							strSystemDbType = strArrParaTemp[1];
							parameters[i] = this.DataProvider.GetParameter();
							parameters[i].ParameterName = this.ParameterChar + parameterName;
							parameters[i].DbType = (DbType)System.Enum.Parse(typeof(DbType), strSystemDbType.Trim());
							break;
						case 3:
							//并且声明了数据长度
							parameterType = strArrParaTemp[0];
							strSystemDbType = strArrParaTemp[1];
							dbType = (DbType)Enum.Parse(typeof(DbType), strSystemDbType.Trim());
							if(strArrParaTemp[2] == string.Empty)
							{
								size = this.GetDefaultSize(dbType);
							}
							else
							{
								size = Int32.Parse(strArrParaTemp[2].Trim());
							}
							parameters[i] = this.DataProvider.GetParameter(this.ParameterChar + parameterName, dbType, size, ParameterDirection.Input);
							break;
						case 4:
							//并且声明了参数输入输出类型
							parameterType = strArrParaTemp[0];
							strSystemDbType = strArrParaTemp[1].Trim(); //处理Decimal的精度问题 Decimal(14.2)，表示精度14位，小数 2 位。
							strParameterDirection = strArrParaTemp[3].Trim();
							var direction = default(ParameterDirection);
							dbType = (DbType)Enum.Parse(typeof(DbType), strSystemDbType.Trim());
							if(strArrParaTemp[2] == string.Empty)
							{
								size = GetDefaultSize(dbType);
							}
							else
							{
								size = Int32.Parse(strArrParaTemp[2].Trim());
							}
							direction = (ParameterDirection)Enum.Parse(typeof(ParameterDirection), strParameterDirection);
							parameters[i] = this.DataProvider.GetParameter(this.ParameterChar + parameterName, dbType, size, direction);
							break;
						default:
							//并且声明了参数输入输出类型
							parameterType = strArrParaTemp[0];
							strSystemDbType = strArrParaTemp[1].Trim(); //处理Decimal的精度问题 Decimal(14.2)，表示精度14位，小数 2 位。
							strParameterDirection = strArrParaTemp[3].Trim();
							ParameterDirection direction1 = default(ParameterDirection);
							dbType = (DbType)Enum.Parse(typeof(DbType), strSystemDbType.Trim());
							if(strArrParaTemp[2] == string.Empty)
							{
								size = GetDefaultSize(dbType);
							}
							else
							{
								size = Int32.Parse(strArrParaTemp[2].Trim());
							}
							direction1 = (ParameterDirection)Enum.Parse(typeof(ParameterDirection), strParameterDirection);
							byte precision = 18; //精度
							byte scale = 4; //小数位

							if(strArrParaTemp.Length == 5)
							{
								if(strArrParaTemp[4] != string.Empty)
								{
									precision = byte.Parse(strArrParaTemp[4]);
								}
							}
							if(strArrParaTemp.Length == 6)
							{
								if(strArrParaTemp[5] != string.Empty)
								{
									scale = byte.Parse(strArrParaTemp[5]);
								}
							}
							parameters[i] = this.DataProvider.GetParameter(this.ParameterChar + parameterName, dbType, size, direction1, precision, scale);
							break;
					}
				}
				else
				{
					parameters[i] = this.DataProvider.GetParameter();
					parameters[i].ParameterName = this.ParameterChar + parameterName;
				}
			}
			return parameters;
		}

		private DbType ConvertType(string type)
		{
			type = type.ToLower();
			if(type.Length > 7 && type.Substring(0, 7) == "system.")
			{
				type = type.Substring(7);
			}
			switch(type)
			{
				case "boolean":
					//System.Boolean
					return DbType.Boolean;
				case "byte":
					//System.Byte
					return DbType.Byte;
				case "char":
					//System.Char
					return DbType.AnsiStringFixedLength;
				case "datetime":
					//System.DateTime
					return DbType.DateTime;
				case "decimal":
					//System.Decimal
					return DbType.Decimal;
				case "double":
					//System.Double
					return DbType.Double;
				case "guid":
					//System.Guid
					return DbType.Guid;
				case "int16":
					//System.Int16
					return DbType.Int16;
				case "int32":
					//System.Int32
					return DbType.Int32;
				case "int64":
					//System.Int64
					return DbType.Int64;
				case "single":
					//System.Single
					return DbType.Single;
				case "string":
					//System.String
					return DbType.String;
				case "byte[]":
					//System.Byte[]
					return DbType.Binary;
				case "type":
					//System.Type
					return DbType.String;
				default:
					return DbType.Object;
			}
		}

		private int GetDefaultSize(DbType type)
		{
			switch(type)
			{
				case DbType.String:
					return 255;
				case DbType.AnsiString:
					return 50;
				case DbType.AnsiStringFixedLength:
					return 50;
				case DbType.Binary:
					return 50;
				case DbType.Boolean:
					return 1;
				case DbType.Byte:
					return 1;
				case DbType.Currency:
					return 8;
				case DbType.Date:
					return 4;
				case DbType.DateTime:
					return 8;
				case DbType.Decimal:
					return 9;
				case DbType.Double:
					return 16;
				case DbType.Guid:
					return 16;
				case DbType.Int16:
					return 2;
				case DbType.Int32:
					return 4;
				case DbType.Int64:
					return 8;
				case DbType.Object:
					return 4;
				case DbType.SByte:
					return 2;
				case DbType.Single:
					return 8;
				case DbType.StringFixedLength:
					return 50;
				case DbType.Time:
					return 8;
				default:
					return 4;
			}
		}

		#endregion
	}
}